using Pegasus.Diagnostics;
using System;
using System.Collections;
using System.Threading;

namespace Pegasus.Threading
{
	/// <summary>
	/// Summary description for AppThreadPool.
	/// </summary>
	public class AppThreadPool
	{
		// Local Instnace Values
		private int m_maxThreads = -1;

		private Queue m_availableThreads = new Queue();
		private ArrayList m_workingThreads = new ArrayList();

		private bool m_queueWorkItems = true;
		private Queue m_pendingQueue = new Queue();

		// Local Static Values
		private static AppThreadPool s_sysPool = null;

		/// <summary>
		/// Default Constructor
		/// </summary>
		public AppThreadPool()
		{
			AppDomain.CurrentDomain.DomainUnload += new EventHandler(CurrentDomain_DomainUnload);
		}

		/// <summary>
		/// Gets or sets a value indicating whether work items should be queued 
		/// up if the manimum number of threads has been reached.
		/// </summary>
		/// <value><c>true</c> if work items will be queue; otherwise, <c>false</c>.</value>
		public bool QueueWorkItems
		{
			get
			{
				return m_queueWorkItems;
			}

			set
			{
				m_queueWorkItems = value;
			}
		}


		/// <summary>
		/// Get/Set the maximun number of thread in the thread pool.
		/// </summary>
		/// <remarks>If a -1 is returned then the pool will grow as needed</remarks>
		public int MaximumThreads
		{
			get
			{
				return m_maxThreads;
			}

			set
			{
				if( value < -1 || value == 0 )
				{
					throw new ArgumentException( "MaximumThreads can only be set to -1 or a positive value" );
				}

				m_maxThreads = value;
			}
		}

		/// <summary>
		/// Get total number of thread in the pool
		/// </summary>
		public int TotalThreadCount
		{
			get
			{
				lock( this )
				{
					return m_workingThreads.Count + m_availableThreads.Count;
				}
			}
		}

		/// <summary>
		/// Get number of working threads 
		/// </summary>
		public int WorkingThreadCount
		{
			get
			{
				lock( this )
				{
					return m_workingThreads.Count;
				}
			}
		}

		/// <summary>
		/// Get number of available (idle) threads
		/// </summary>
		public int AvailableThreadCount
		{
			get
			{
				lock( this )
				{
					return m_availableThreads.Count;
				}
			}
		}

		/// <summary>
		/// Gets the number of pending work items in the queue.
		/// </summary>
		/// <value>The number work items in the queue.</value>
		public int PendingWorkItems
		{
			get
			{
				return m_pendingQueue.Count;
			}
		}

		/// <summary>
		/// Clears the pending queue.
		/// </summary>
		public void ClearPendingQueue()
		{
			m_pendingQueue.Clear();
		}

		/// <summary>
		/// Wait for all the current worker threads to exit.
		/// </summary>
		/// <returns>
		/// If true then all the threads exited, if false then 
		/// the system timed out before all worker threads exited.
		/// </returns>
		public void JoinWorkers()
		{
			JoinWorkers( int.MaxValue );
		}

		/// <summary>
		/// Wait for all the current worker threads to exit.
		/// </summary>
		/// <param name="wait">The time to wait</param>
		/// <returns>
		/// If true then all the threads exited, if false then 
		/// the system timed out before all worker threads exited.
		/// </returns>
		public bool JoinWorkers( TimeSpan wait )
		{
			return JoinWorkers( (int) wait.TotalMilliseconds );
		}

		/// <summary>
		/// Wait for all the current worker threads to exit.
		/// </summary>
		/// <param name="milliseconds">The number of milliseconds to wait</param>
		/// <returns>
		/// If true then all the threads exited, if false then 
		/// the system timed out before all worker threads exited.
		/// </returns>
		public bool JoinWorkers( int milliseconds )
		{
			AppThread[] appThreads = null;
			DateTime stop = DateTime.Now.AddMilliseconds( milliseconds );

			lock( this )
			{
				appThreads = (AppThread[]) m_workingThreads.ToArray( typeof( AppThread ) );
				if( appThreads.Length == 0 )
				{
					return true;
				}
			}
			
			bool allDone = false;
			while( !allDone )
			{
				allDone = true;

				// Yeald off the thread
				Thread.Sleep( 1 );

				// Check the threads and see if they done or not
				int count = appThreads.Length;
				for( int x = 0; x < count; x++ )
				{
					AppThread thread = appThreads[ x ]; 
					if( thread != null )
					{
						if( thread.IsRunning )
						{
							allDone = false;
							break;
						}
						else
						{
							// This thread is done so remove it from the array.
							appThreads[ x ] = null;
						}
					}
				}
				
				if( stop <= DateTime.Now )
				{
					break;
				}
			}

			return allDone;
		}

		/// <summary>
		/// Blocks the calling thread until all the available threads have been terminated.
		/// </summary>
		public void ReleaseAvailableThreads()
		{
			ArrayList list = new ArrayList();

			lock( this )
			{
				if( m_availableThreads.Count > 0 )
				{
					AppThread thread = (AppThread) m_availableThreads.Dequeue();
					while( thread != null )
					{
						thread.Stop();
						list.Add( thread );

						try
						{
							thread = (AppThread) m_availableThreads.Dequeue();
						}
						catch
						{
							thread = null;
						}
					}
				}
			}

			// Release the lock and now wait for all the threads to finish up
			foreach( AppThread thread in list )
			{
				thread.Join();
			}
		}

		/// <summary>
		/// Blocks the calling thread until all the working threads have been terminated.
		/// It will call the Abort() on all working threads threads.
		/// </summary>
		/// <remarks>
		/// Use this method with caution
		/// </remarks>
		public void AbortWorkingThreads()
		{
			ArrayList list = new ArrayList();

			lock( this )
			{
				foreach( AppThread thread in m_workingThreads )
				{
					thread.Abort();
					list.Add( thread );
				}

				// Release the lock and now wait for all the threads to finish up
				foreach( AppThread thread in list )
				{
					m_workingThreads.Remove( thread );
				}
			}
		}

		/// <summary>
		/// Clears the pending queue aborts working threads and releases available threads
		/// </summary>
		public void AbortAllThreads()
		{
			ClearPendingQueue();
			AbortWorkingThreads();
			ReleaseAvailableThreads();
		}

		/// <summary>
		/// This will queue up an item to be run on the thread pool.
		/// </summary>
		/// <param name="callback">The handler for the Thread proc to run.</param>
		/// <returns>
		/// Returns 1 if the work item was dispatched to a thread.  Returns 2 
		/// if the work items was queued because the maximum number of working 
		/// threads has been was reached.  Returns 0 if the work items was
		/// not queued up because the maximum number of working threads
		/// has been reached and QueueWorkItems is set to false.
		/// </returns>
		public int QueueUserWorkItem( WaitCallback callback )
		{
			return QueueUserWorkItem( callback, null );
		}

		/// <summary>
		/// This will queue up an item to be run on the thread pool.
		/// </summary>
		/// <param name="callback">The handler for the Thread proc to run.</param>
		/// <param name="state">A state object passed to the thread proc</param>
		/// <returns>
		/// Returns 1 if the work item was dispatched to a thread.  Returns 2 
		/// if the work items was queued because the maximum number of working 
		/// threads has been was reached.  Returns 0 if the work items was
		/// not queued up because the maximum number of working threads
		/// has been reached and QueueWorkItems is set to false.
		/// </returns>
		public int QueueUserWorkItem( WaitCallback callback, object state )
		{
			lock( this )
			{
				// Max threads reached so queue the work item
				AppThread workingThread = GetWorkingThread();
				if( workingThread == null )
				{
					if( m_queueWorkItems )
					{
						m_pendingQueue.Enqueue( new ItemInfo( callback, state ) );
						return 2;
					}

					return 0;
				}

				// Launch the working thread
				m_workingThreads.Add( workingThread );

				workingThread.Callback = callback;
				workingThread.StateObject = state;
				workingThread.Run();

				return 1;
			}
		}

		/// <summary>
		/// Creates a new worker thread
		/// </summary>
		/// <returns>Referece to the new worker thread</returns>
		private AppThread CreateNewThread()
		{
			AppThread workingThread = null;

			lock( this )
			{

				if( m_maxThreads == -1 || TotalThreadCount < m_maxThreads )
				{
					workingThread = new AppThread();
					workingThread.ThreadCompleted += new AppThread.ThreadCompletedHandler( WorkingThread_ThreadCompleted );
					workingThread.UnhandledException += new AppThread.UnhandledExceptionHandler( WorkingThread_UnhandledException );
				}
			}

			return workingThread;
		}

		/// <summary>
		/// Get the next avaliable worker thread
		/// </summary>
		/// <returns>
		/// Returns a worker thread.  If null then no worker threads are availabe
		/// and the max threads in the pool has been reached
		/// </returns>
		private AppThread GetWorkingThread()
		{
			AppThread workingThread = null;

			lock( this )
			{
				if( m_availableThreads.Count != 0 )
				{
					workingThread = (AppThread) m_availableThreads.Dequeue();
				}

				// No thread available so create a new one
				if( workingThread == null )
				{
					workingThread = CreateNewThread();
				}
			}

			return workingThread;
		}

		/// <summary>
		/// Called when the worker thread completes it's task
		/// </summary>
		private void WorkingThread_ThreadCompleted( AppThread workingThread )
		{
			lock( this )
			{
				m_workingThreads.Remove( workingThread );
				m_availableThreads.Enqueue( workingThread );

				if( m_pendingQueue.Count > 0 )
				{
					ItemInfo info = (ItemInfo) m_pendingQueue.Dequeue();
					if( info != null )
					{
						QueueUserWorkItem( info.m_callback, info.m_state );
					}
				}
			}
		}

		/// <summary>
		/// Called if there is an unhandled exception on a worker thread.
		/// </summary>
		/// <param name="thread">The thread that caused the exception</param>
		/// <param name="e">The exception that was thrown</param>
		private void WorkingThread_UnhandledException( AppThread thread, Exception e )
		{
			lock( this )
			{
				// Remove the thread from the working list. We are just going to let the system
				// drop the thread and clean up the object.  We have no idea what state things 
				// are in.  The AppThread object will rethrow the exception.
				m_workingThreads.Remove( thread );

				// Fire the exception event
				if( UnhandledException != null )
				{
					UnhandledException( thread.Callback, thread.StateObject, e );
				}
			}
		}

		/// <summary>
		/// When the current domain exits then release all our threads
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="args"></param>
		private void CurrentDomain_DomainUnload( object sender, EventArgs args )
		{
			AbortAllThreads();
			s_sysPool = null;
		}

		/// <summary>
		/// Get the current system thread pool
		/// </summary>
		public static AppThreadPool SystemPool
		{
			get
			{
				if( s_sysPool == null )
				{
					s_sysPool = new AppThreadPool();
				}

				return s_sysPool;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		private class ItemInfo
		{
			public WaitCallback m_callback;
			public object m_state;

			/// <summary>
			/// 
			/// </summary>
			/// <param name="callback"></param>
			/// <param name="state"></param>
			public ItemInfo( WaitCallback callback, object state )
			{
				m_callback = callback;
				m_state = state;
			}
		}

		/// <summary>
		/// The delegate for the unhandled exceptions.
		/// </summary>
		/// <param name="callback"></param>
		/// <param name="state"></param>
		/// <param name="e"></param>
		public delegate void UnhandledExceptionHandler( WaitCallback callback, object state, Exception e );

		/// <summary>
		/// Fired where the workflow service catches an exception that no one else processed.
		/// </summary>
		public event UnhandledExceptionHandler UnhandledException;
	}
}
